/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | www.openfoam.com
     \\/     M anipulation  |
-------------------------------------------------------------------------------
    Copyright (C) 2017-2019 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

    OpenFOAM is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.

    You should have received a copy of the GNU General Public License
    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.

Application
    Test-primitives

Description
    Parsing etc for primitives.

\*---------------------------------------------------------------------------*/

#include "scalar.H"
#include "FlatOutput.H"
#include "ListStream.H"
#include "StringStream.H"
#include "NASCore.H"
#include "parsing.H"
#include "Tuple2.H"
#include "Switch.H"
#include "dictionary.H"

using namespace Foam;

// Shadow fileFormats::NASCore::readNasScalar
inline scalar readNasScalar(const std::string& str)
{
    return fileFormats::NASCore::readNasScalar(str);
}


// As a function
inline Switch readSwitch(const std::string& str)
{
    Switch sw(str);

    if (sw.type() == Switch::ON)
    {
        Info<< "Was 'on'" << nl;
    }

    return sw;
}


void printInfo(const Switch& sw)
{
    Info<<"Switch " << sw.c_str() << " (enum=" << label(sw.type()) << ")\n";
}


Ostream& toString(Ostream& os, const UList<char>& list)
{
    os << '"';
    for (const char c : list)
    {
        os << c;
    }
    os << '"';

    return os;
}


// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

template<class T>
bool hadParsingError
(
    const std::pair<bool, std::string>& input,
    const std::pair<bool, T>& result,
    std::string errMsg
)
{
    if (result.first)
    {
        if (input.first)
        {
            Info<< "(pass) parsed "
                << input.second << " = " << result.second << nl;
        }
        else
        {
            Info<< "(fail) unexpected success for " << input.second << nl;
        }
    }
    else
    {
        if (input.first)
        {
            Info<< "(fail) unexpected";
        }
        else
        {
            Info<< "(pass) expected";
        }

        Info<< " failure " << input.second << "  >> " << errMsg.c_str() << nl;
    }

    return (input.first != result.first);
}


template<class T>
unsigned testParsing
(
    T (*function)(const std::string&),
    std::initializer_list<std::pair<bool, std::string>> tests
)
{
    unsigned nFail = 0;
    string errMsg;

    // Expect some failures
    const bool prev1 = FatalError.throwExceptions();
    const bool prev2 = FatalIOError.throwExceptions();

    for (const std::pair<bool, std::string>& test : tests)
    {
        std::pair<bool, T> result(false, T());

        try
        {
            result.second = function (test.second);
            result.first = true;
        }
        catch (const Foam::error& err)
        {
            errMsg = err.message();
        }

        if (test.first != result.first)
        {
            ++nFail;
        }

        hadParsingError(test, result, errMsg);
    }

    FatalError.throwExceptions(prev1);
    FatalIOError.throwExceptions(prev2);

    return nFail;
}


int main(int argc, char *argv[])
{
    unsigned nFail = 0;

    {
        Info<< nl << "Test Switch parsing:" << nl;
        nFail += testParsing
        (
            &readSwitch,
            {
                { false, "True" },
                { true,  "false" },
                { true,  "on" },
                { false, "None" },
                { false, "default" },
            }
        );

        Info<< nl << "Test Switch defaults:" << nl;

        dictionary dict;
        dict.add("key1" , "true");
        dict.add("key2" , "off");

        for (const word& k : { "key", "key1", "key2" })
        {
            Switch sw1(k, dict, Switch::YES);
            Switch sw2(k, dict, Switch::NO);

            bool sw3(Switch(k, dict, Switch::YES));

            printInfo(sw1);
            printInfo(sw2);
            Info<<"bool " << sw3 << nl;
        }
    }

    {
        Info<< nl << "Test readDouble: (small=" << doubleScalarVSMALL
            << " great=" << doubleScalarVSMALL << "):" << nl;
        nFail += testParsing
        (
            &readDouble,
            {
                { false, "" },
                { false, "  " },
                { false, " xxx " },
                { false, " 1234E-" },
                { false, " 1234E junk" },
                { true,  " 3.14159 " },
                { true,  " 31.4159E-1 "  },
                { false, " 100E1000 "  },
                { true,  " 1E-40 "  },
                { true,  " 1E-305 "  },
                { true,  " 1E-37 "  },
                { true,  " 1E-300 "  },
            }
        );
    }

    {
        Info<< nl << "Test readFloat: (small=" << floatScalarVSMALL
            << " great=" << floatScalarVGREAT << "):" << nl;

        nFail += testParsing
        (
            &readFloat,
            {
                { true,  " 3.14159 " },
                { true,  " 31.4159E-1 "  },
                { false, " 31.4159E200 "  },
                { true,  " 31.4159E20 "  },
                { true,  " 1E-40 "  },
                { true,  " 1E-305 "  },
                { true,  " 1E-37 "  },
                { true,  " 1E-300 "  },
            }
        );
    }

    {
        Info<< nl << "Test readNasScalar:" << nl;
        nFail += testParsing
        (
            &readNasScalar,
            {
                { true,  " 3.14159 " },
                { true,  " 31.4159E-1 "  },
                { true,  " 314.159-2 "  },
                { true,  " 31.4159E200 "  },
                { true,  " 31.4159E20 "  },
                { true,  " 1E-40 "  },
                { true,  " 1E-305 "  },
                { true,  " 1E-37 "  },
                { true,  " 1E-300 "  },
            }
        );
    }

    {
        Info<< nl << "Test readInt32 (max=" << INT32_MAX << "):" << nl;
        nFail += testParsing
        (
            &readInt32,
            {
                { false, " 3.14159 " },
                { false, " 31E1 " },
                { false, " 31.4159E-1 "  },
                { true,  "100"  },
                { true,  "\t2147483644"  },
                { false, "   2147483700  "  },
            }
        );
    }

    {
        Info<< nl << "Test readUint32 (max="
            << unsigned(UINT32_MAX) << "):" << nl;
        nFail += testParsing
        (
            &readUint32,
            {
                { true,  "\t2147483644"  },
                { true,  " 2147483700  "  },
                { true,  " 4294967295  "  },
                { false, " 4294968000  "  },
            }
        );
    }

    if (true)
    {
        #ifdef WM_DP
        typedef float otherType;
        #else
        typedef double otherType;
        #endif

        Info<< nl << "Test raw binary read of scalar list:"
            << " write " << sizeof(otherType)
            << " read " << sizeof(scalar) << nl;

        List<otherType> srcList(15);
        forAll(srcList, i)
        {
            srcList[i] = 1 + 10*i;
        }

        DynamicList<char> buf;

        OListStream os(std::move(buf), IOstream::BINARY);
        os << srcList;

        os.swap(buf); // Recover buffer

        // Read back
        List<scalar> dstList;

        UIListStream is(buf, IOstream::BINARY);
        is.setScalarByteSize(sizeof(otherType));

        Info<< "Stream scalar-size ("
            << is.scalarByteSize() << ") is native: "
            << Switch(is.checkScalarSize<otherType>()) << nl;


        token firstToken(is);
        Info<< "List has " << firstToken.info() << " scalar items" << nl;

        dstList.resize(firstToken.labelToken(), 3.14159);

        is.beginRawRead();
        // for (scalar& val : dstList)
        // {
        //     val = readRawScalar(is);
        // }
        readRawScalar(is, dstList.data(), dstList.size());
        is.endRawRead();

        Info<< "Wrote " << flatOutput(srcList) << nl
            << "Read " << flatOutput(dstList) << nl;
    }

    if (nFail)
    {
        Info<< nl << "failed " << nFail << " tests" << nl;
        return 1;
    }

    Info<< nl << "passed all tests" << nl;
    return 0;
}

// ************************************************************************* //
